import sys
import os
from PyQt5.QtWidgets import QApplication, QWidget, QMessageBox, QTableWidgetItem
from PyQt5 import QtGui
from PyQt5.QtCore import pyqtSlot, pyqtSignal
import pandas as pd
import akshare as ak
from datetime import date
from typing import Callable

from pts.task_engine import TaskEngine, Task
from pts.constant import TaskType
from .Ui_ak_stocklist import Ui_AkStockList
from ..global_functions import displayDataFrame, getAmountFromValue
from ..database import OpenAkStockListDb
from ..web_crawler import stock_sector_fund_flow_his
from .candle_chart_dialog import CandleChartDialog
from ..setting import SETTINGS

# 导入matplotlib模块并使用Qt5Agg
import matplotlib
matplotlib.use('Qt5Agg')
# 使用 matplotlib中的FigureCanvas (在使用 Qt5 Backends中 FigureCanvas继承自QtWidgets.QWidget)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt


class AkStockList(QWidget, Ui_AkStockList):
    """3-个股资金流 页面"""
    # 个股资金流的文件名
    file_stocklist = r'Data\ak_stocklist.csv'

    # 用于处理自动刷新完成信息的信号
    signal: pyqtSignal = pyqtSignal(str)

    def __init__(self, parent, task_engine: TaskEngine, displayStatusMess: Callable):
        """初始化"""
        super(AkStockList, self).__init__()
        self.setupUi(self)
        self.mainWindow = parent

        # 从主窗口接受到的任务引擎
        self.task_engine: TaskEngine = task_engine
        # 主窗口的信息显示函数
        self.displayStatusMess: Callable = displayStatusMess

        self.stock_list = None

        self.initUi()
        # 初始化与本页面相关的任务
        self.initTask()

    def initTask(self):
        """初始化与本页面相关的任务"""
        # 盘中任务
        task = Task(TaskType.pz, self.process_task, self.signal.emit, interval=SETTINGS["RW.spPZ_3"])
        self.task_engine.put(task)

        # 盘后任务
        task = Task(TaskType.ph, self.process_task, self.signal.emit, task_id=3)
        self.task_engine.put(task)

        # 为信号关联槽函数
        self.signal.connect(self.display_task)

    def process_task(self) -> str:
        """
        任务的执行函数，供任务引擎的守护线程调用
        :return: 任务执行的结果信息
        """
        return self.reloadStockList()

    def display_task(self, mess: str):
        """
        任务执行结果的显示函数，供任务引擎的守护线程调用
        :param mess: 任务执行的结果信息
        :return: 无
        """
        # 在主窗口的状态栏上显示结果信息
        self.displayStatusMess(mess)
        # 更新页面上的列表
        self.displayStockList()


    def initUi(self):
        '''初始化界面'''
        # 初始化历史列表的表头及图表
        self.initChart()

        self.displayStockList()

        self.splitter.setStretchFactor(0, 3)
        self.splitter.setStretchFactor(1, 1)

        #self.twStockList.itemDoubleClicked.connect(self.on_twStockList_itemDoubleClicked)

        # Candle Chart
        self.candle_dialog = CandleChartDialog()

    def initChart(self):
        # 初始化历史列表的表头
        headers = ['日期', '涨幅', '主力净流入', '超大单净流入', '大单净流入', '中单净流入', '小单净流入']
        cnum = len(headers)
        self.twFundFlowHistory.setColumnCount(cnum)
        self.twFundFlowHistory.setHorizontalHeaderLabels(headers)

        # 初始化图表
        self.figure = plt.figure()
        self.canvas = FigureCanvas(self.figure)

        # 在Figure对象中创建一个Axes对象，每个Axes对象即为一个绘图区域
        self.ax = self.figure.add_subplot(111)

        self.vLayoutHistory.addWidget(self.canvas)

    def displayStockList(self):
        if not os.path.exists(self.file_stocklist):
            self.reloadStockList()

        stock_data = pd.read_csv(self.file_stocklist, dtype={'代码': str})
        displayDataFrame(stock_data, self.twStockList, amountList=[5, 7, 9, 11, 13], startCol=1, orderCol=[5, ])
        self.stock_list = list(stock_data['代码'])

    def reloadStockList(self) -> str:
        try:
            stock_data = ak.stock_individual_fund_flow_rank(indicator="今日")
            stock_data.to_csv(self.file_stocklist, index=False)
        except BaseException as ex:
            return '数据下载失败：{}。'.format(ex.args)

        conn = OpenAkStockListDb()
        if not conn:
            return '打开数据库失败。'

        sqlStr = "insert into AkStockList values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
        da = date.today()                    # 当前日期
        try:
            # 先删除原有数据
            conn.execute("delete from AkStockList where rq=?", (da,))

            # 插入新数据
            for index, row in stock_data.iterrows():
                record = (da, row[1], row[2], row[3], row[4], row[5], row[6], row[7], row[8], row[9], row[10], row[11], row[12], row[13], row[14])
                conn.execute(sqlStr, record)
            conn.commit()
        except BaseException as ex:
            conn.close()
            return '向数据库插入数据失败：{}。'.format(ex.args)
        conn.close()

        return '刷新个股资金流向完成。'

    def reloadOneStockHis(self, stock_name: str, stock_code: str):
        '''重新下载当日和5日的板块资金流向，并存入CSV文件，并将当日的存入数据库'''
        # 重新下载当日和5日的板块资金流向，并存入CSV文件
        stock_data = None
        try:
            stock_data = stock_sector_fund_flow_his(stock_code, "个股")
        except TypeError:
            QMessageBox.information(self, '提示信息', '由于服务器的原因，无法下载数据。')
        except BaseException as ex:
            QMessageBox.information(self, '提示信息', '数据下载失败：{}。'.format(ex.args))
        if stock_data is None:
            return

        # 将当日的存入数据库
        conn = OpenAkStockListDb()
        if not conn:
            QMessageBox.information(self, '提示信息', '打开数据库失败。')

        sqlStr = "insert into AkStockList values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"
        da = date.today()                    # 当前日期
        try:
            # 先删除原有数据
            da1 = stock_data.iloc[:, 0].dropna().min()
            da2 = stock_data.iloc[:, 0].dropna().max()
            conn.execute("delete from AkStockList where dm = ? and rq >= ? and rq <= ?", (stock_code, da1, da2))

            # 插入新数据
            for index, row in stock_data.iterrows():
                record = (row[0], stock_code, stock_name, row[11], row[12], row[1], row[6], row[5], row[10], row[4], row[9], row[3], row[8], row[2], row[7])
                conn.execute(sqlStr, record)
            conn.commit()
        except BaseException as ex:
            QMessageBox.information(self, '提示信息', '向数据库插入数据失败：{}。'.format(ex.args))
        conn.close()

        QMessageBox.information(self, '提示信息', '刷新资金流向完成。')

    def displayCurStock(self, reload: int):
        '''显示某个股票的信息
        reload: 1-刷新资金历史；0-不刷新'''

        # 取当前股票代码
        row = self.twStockList.currentRow()
        if row < 0:
            self.twFundFlowHistory.setRowCount(0)
            plt.cla()
            return

        item = self.twStockList.item(row, 0)
        stock_code = item.text()

        # 如果需要，从网上爬取数据
        if reload == 1:
            # 取股票名称
            item = self.twStockList.item(row, 1)
            stock_name = item.text()
            # 重新下载某个股票的资金历史，并存入数据库
            self.reloadOneStockHis(stock_name, stock_code)

        # 从数据库中取历史资金
        conn = OpenAkStockListDb()
        if not conn:
            QMessageBox.information(self, '提示信息', '打开数据库失败。')

        try:
            sqlStr = "select * from AkStockList where dm='{}' order by rq desc".format(stock_code)
            cursor = conn.execute(sqlStr)

            # fields = ['rq', 'zf', 'zlje', 'cdde', 'dde', 'zde', 'xde']
            fields = [0, 4, 5, 7, 9, 11, 13]
            index = 0
            xs = []
            ysr = []
            ysg = []
            for row in cursor:
                va = 0
                self.twFundFlowHistory.setRowCount(index + 1)
                for fi in range(len(fields)):
                    istr = str(row[fields[fi]])
                    if fi > 1:
                        istr = getAmountFromValue(row[fields[fi]])
                    item = QTableWidgetItem(istr)
                    if fi == 2:
                        try:
                            va = float(row[fields[fi]])
                            if va > 0:
                                item.setForeground(QtGui.QColor(255, 0, 0))
                            else:
                                item.setForeground(QtGui.QColor(0, 255, 0))
                        except BaseException as ex:
                            va = 0

                    self.twFundFlowHistory.setItem(index, fi, item)

                # 图表上只显示最近若干天的数据
                index = index + 1
                if index > 20:
                    continue

                # 加入到坐标轴数据
                xs.insert(0, row[0])
                if va < 0:
                    ysr.insert(0, 0)
                    #ysg.insert(0, row[fields[2]])
                    ysg.insert(0, va)
                else:
                    ysr.insert(0, va)
                    ysg.insert(0, 0)
        except BaseException as ex:
            QMessageBox.information(self, '提示信息', '从数据库中取数据失败：{}。'.format(ex.args))
        conn.close()

        self.ax.clear()
        self.ax.bar(xs, ysr, facecolor='red')
        self.ax.bar(xs, ysg, facecolor='green')

        self.figure.autofmt_xdate()
        for label in self.ax.xaxis.get_ticklabels():
            # label is a Text instance
            #label.set_color('red')
            label.set_rotation(45)
            #label.set_fontsize(16)

        self.canvas.draw()

    @pyqtSlot()
    def on_btnRefreshStockHis_clicked(self):
        '''刷新历史资金列表'''
        self.displayCurStock(1)

    @pyqtSlot()
    def on_btnRefresh_clicked(self):
        '''刷新'''
        # 重新下载个股资金流向
        QMessageBox.information(self, '提示信息', self.reloadStockList())
        # 显示个股资金流向
        self.displayStockList()

    @pyqtSlot()
    def on_twStockList_itemSelectionChanged(self):
        '''切换当前股票'''
        self.displayCurStock(0)

    def on_twStockList_itemDoubleClicked(self):
        # 取当前股票代码
        row = self.twStockList.currentRow()
        if row < 0:
            self.twFundFlowHistory.setRowCount(0)
            plt.cla()
            return

        item = self.twStockList.item(row, 0)
        vt_symbol = item.text()

        self.candle_dialog.set_vt_symbol(vt_symbol)
        self.candle_dialog.exec_()

    @pyqtSlot()
    def on_edtSearch_returnPressed(self):
        '''在查找输入框中按了回国键'''
        self.on_btnSearch_clicked()

    @pyqtSlot()
    def on_btnSearch_clicked(self):
        '''查找个股'''
        # 要查找的字符串
        find_str = self.edtSearch.text()

        # 取股票数
        rowcount = self.twStockList.rowCount()
        if rowcount <= 0:
            return

        # 当前行号
        row = self.twStockList.currentRow()
        if row < 0:
            row = 0
        else:
            row = row + 1

        hasFind = -1
        for i in range(row, rowcount):
            item = self.twStockList.item(i, 0)
            item_str = item.text()
            if item_str.find(find_str) >= 0:
                hasFind = i
                break
            item = self.twStockList.item(i, 1)
            item_str = item.text()
            if item_str.find(find_str) >= 0:
                hasFind = i
                break

        if hasFind < 0:
            for i in range(row):
                item = self.twStockList.item(i, 0)
                item_str = item.text()
                if item_str.find(find_str) >= 0:
                    hasFind = i
                    break
                item = self.twStockList.item(i, 1)
                item_str = item.text()
                if item_str.find(find_str) >= 0:
                    hasFind = i
                    break

        if hasFind >= 0:
            self.twStockList.setCurrentCell(hasFind, 0)

    @pyqtSlot()
    def on_btnKLine_clicked(self):
        """K线图表"""
        # 取当前股票代码
        row = self.twStockList.currentRow()
        if row < 0:
            self.twFundFlowHistory.setRowCount(0)
            return

        item = self.twStockList.item(row, 0)
        vt_symbol = item.text()

        self.candle_dialog.set_vt_symbol(vt_symbol)
        self.candle_dialog.exec_()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui = AkStockList()
    ui.show()
    sys.exit(app.exec_())
